<?php

namespace Shared;

use Shared\Doctrine\DBAL\DataType\IpType;
use Doctrine\ORM\EntityManager;

class Application {

    const ENTITY_NAMESPACE = 'Shared\Main';

    private static $_instance;
    
    /**
     * @var \Shared\LRC\LRCSupport
     */
    private $LRCSupport;
    private $_entityManager;
    private $_config;
    private $_loader;
    private $_twig;
    private $_frontController;
    private $CurrentUser;
    private $host;
    private $_request;
    private $currentRoute;
    private $locale;
    private $filesDir;
    private $dir = '';
    private $environment  = 'dev';
    private $configFolder = 'Shared/config';
    private $deleteCache  = false;

    /**
     * @var \Doctrine\Common\Annotations\CachedReader
     */
    private $AnnotationReader;

    private function __construct() {
        $this->filesDir = ROOT_DIR . 'data' . DS . 'files';
        $this->_loader  = require ROOT_DIR . 'autoload.php';
    }

    public function setCurrentUser($User = null) {
        if (!$User) {
            $User = isset($_SESSION['user']) && $_SESSION['user'] ? $_SESSION['user'] : null;
        }

        $this->CurrentUser = $User;
    }

    public function getCurrentUser() {
        return isset($_SESSION['user']) && $_SESSION['user'] ? $_SESSION['user'] : null;
    }

    public function getLocaleFromRequest() {
//        $str = $_SERVER['REQUEST_URI'];
//        $str = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['REQUEST_URI']);
        $str = $_SERVER['REQUEST_URI'];

        if (basename($_SERVER['SCRIPT_NAME']) != 'app.php') {
            $str = str_replace('/' . basename($_SERVER['SCRIPT_NAME']), '', $str);
        }

        if ($str && (strlen($str) == 3 || strpos($str, '/', 1) !== false)) {
            $matches = array();
            preg_match('/^\/([A-Za-z0-9\'\/~`\!@#\$%\^&\*\(\)_\-\+=\{\}\[\]\|;:"\<\>,\.\?\\\]{2})/', $str, $matches);

            $locale = $matches[1];

            $_SERVER['REQUEST_URI'] = str_replace('/' . $locale, '', $_SERVER['REQUEST_URI']);

            return $locale;
        }
    }

    private function initLRCSupport() {
        $this->LRCSupport = new \Shared\LRC\LRCSupport();
        if (!$this->locale) {
            $this->locale = $this->getLocale();
        }

        $this->LRCSupport->initCurrentLanguage($this->locale);
    }

    public function getLRCSupport() {
        return $this->LRCSupport;
    }

    public function initLocale() {
        if (!isset($_SESSION)) {
            session_start();
        }

        if (
                strpos($_SERVER['REQUEST_URI'], 'sitemap.xml')   !== 1     &&
                strpos($_SERVER['REQUEST_URI'], 'resources/img') === false &&
                strpos($_SERVER['REQUEST_URI'], '/admin')        === false
        ) {
            $this->locale = $this->getLocaleFromRequest();
        }

        // TODO fix this
        $this->initRequest();

        if (
                strpos($this->_request->getPathInfo(), 'sitemap.xml')   === 1 ||
                strpos($this->_request->getPathInfo(), 'resources/img') == true
        ) {
            $this->locale = 'bg';
        } else if (
                !(strpos($this->_request->getPathInfo(), '/admin') === 0) &&
                !$this->locale
        ) {
            if ($this->_request->getPathInfo() != '/') {
                throw new \Shared\Exceptions\NotFoundException('page not found', 404);
            }

            $this->_request->setLocale('bg');
            header('Location: ' . URLManager::getInstance()->createPath('home'));
            exit;
        } else if (
                strpos($this->_request->getPathInfo(), '/admin') === 0
        ) {
            if ($this->_request->get('locale')) {
                $this->locale = $this->_request->get('locale') ? : 'bg';
                $this->getSession()->set($this->locale, 'locale');
            } else if ($this->getSession()->get('locale')) {
                $this->locale = $this->getSession()->get('locale');
            }
        }
    }

    public function run() {
        if (!$this->locale) {
            $this->initLocale();
        }
        
        $this->_request->setLocale($this->locale);

        $this->setCurrentUser();

        if (!$this->_config) {
            $this->initConfig();
        }
        if (!$this->_entityManager) {
            $this->initEntityManager();
        }

        if (!$this->_twig) {
            $this->initTwig();
        }

        $this->initLRCSupport();

        $this->_frontController = \System\FrontController::getInstance();
        $this->_frontController->setRouter($this->_request);
        $this->_frontController->dispatch();
        $this->setCurrentRoute($this->_frontController->getCurrentRouteName());
        
        if (
            $this->environment == 'prod' && 
            strpos($this->_request->getPathInfo(), '/admin') !== 0 &&
            !in_array($this->_request->getClientIp(), array('78.83.223.209'))
        ) {
            \Shared\Application::instance()->createImpression();
        }
    }

    private function initConfig() {
        $Config = \Shared\Config::getInstance();
        $this->configFolder = ROOT_DIR . $this->configFolder;
        $Config->setConfigFolder($this->configFolder);
        $this->_config = $Config;
    }

    private function initEntityManager() {
        if ($this->_config && $this->_config->doctrine) {
            $this->createDoctrineInstance();
//            $DoctrineManager = new \System\DoctrineManager();
//            $this->_entityManager = $DoctrineManager->createManager($this->_config->doctrine, $this->environment);
//            $this->_entityManager->getEventManager()->addEventListener('gedmo', new \Gedmo\Tree\TreeListener());
        } else {
            throw new \Exception('Cannot create EntityManager');
        }
    }

    private function initTwig() {
        $twigConfig = array('strict_variables' => true);
        if ($this->environment == 'prod') {
            $twigConfig['cache'] = ROOT_DIR . 'data' . DS . 'Cache' . DS . $this->getEnvironment();
        }

        $loader = new \Twig_Loader_Filesystem();
        if (count($this->_config->twig['templatePaths'])) {
            foreach ($this->_config->twig['templatePaths'] as $path => $namespace) {
                $loader->setPaths(ROOT_DIR . $path, $namespace);
            }
        } else {
            throw new \Exception('Twig templates paths not set in config file.');
        }

        $this->_twig = new \Twig_Environment($loader, $twigConfig);

        $this->_twig->addExtension(new \Shared\Extensions\Twig\Path);
        $this->_twig->addExtension(new \Shared\Extensions\Twig\Img);
        $this->_twig->addExtension(new \Shared\Extensions\Twig\Asset);
        $this->_twig->addExtension(new \Shared\Extensions\Twig\Widget);
        $this->_twig->addExtension(new \Shared\Extensions\Twig\LRCSupport);
        $this->_twig->addGlobal('app', $this);
    }

    public function cacheClear() {
        $this->deleteCache = true;
        // Clear Twig Cache
        $this->clearTwigCache();
        // Clear APC Caching
        if (extension_loaded('apc') && ini_get('apc.enabled')) {
            $this->clearApcCache();
        }
    }

    public function clearApcCache() {
        apc_clear_cache();
        apc_clear_cache('user');
        apc_clear_cache('opcode');
    }

    public function clearTwigCache() {
        if (!$this->_twig) {
            $this->initConfig();
            $this->initLocale();
            $this->initEntityManager();
            $this->initLRCSupport();
            $this->initTwig();
        }

        $this->_twig->clearCacheFiles();
    }

    /**
     * 
     * @return \Shared\Application
     */
    public static function getInstance() {
        if (!self::$_instance) {
            self::$_instance = new self;
        }

        return self::$_instance;
    }

    /**
     * 
     * Integrate Hack
     * @return \Shared\Application
     */
    public static function instance() {
        return self::getInstance();
    }

    /**
     * 
     * @return \Doctrine\ORM\EntityManager
     */
    public function getEntityManager() {
        return $this->_entityManager;
    }

    public function setEntityManager($EntityManager) {
        $this->_entityManager = $EntityManager;
    }

    /**
     * 
     * @return \Shared\Config
     */
    public function getConfig() {
        return $this->_config;
    }

    public function setConfig($Config) {
        $this->_config = $Config;
    }

    /**
     * 
     * @return \Composer\Autoload\ClassLoader
     */
    public function getLoader() {
        return $this->_loader;
    }

    public function setLoader($Loader) {
        $this->_loader = $Loader;
    }

    public function getEnvironment() {
        return $this->environment;
    }

    public function setEnvironment($environment) {
        $this->environment = $environment;
    }

    /**
     * 
     * @return \Twig_Environment
     */
    public function getTwig() {
        return $this->_twig;
    }

    public function setTwig($Twig) {
        $this->_twig = $Twig;
    }

    public function getConfigFolder() {
        return $this->configFolder;
    }

    public function setConfigFolder($configFolder) {
        $this->configFolder = $configFolder;
    }

    private function initRequest() {
        $this->_request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
    }

    public function createNotFound($message = 'Page not found!') {
        return $this->createException($message, 404);
    }

    public function createException($content, $code) {
        if (!$this->_request) {
            $this->initRequest();
            $this->initConfig();
            $this->initEntityManager();
            $this->initTwig();
        }

        $this->_request->setLocale('bg');
        $response = new \Symfony\Component\HttpFoundation\Response();

        if ($code == 404) {
            $Template = new \System\Template('@open/Exceptions/error404.twig');
            $content  = $Template->paste();
        } else if ($code == 500) {
            $Template = new \System\Template('@open/Exceptions/error500.twig');
            $content  = $Template->paste();
        }

        $response->setContent($content);
        $response->setStatusCode($code);
        $response->send();
        exit;
    }

    public function createDoctrineInstance() {
        \Doctrine\Common\Annotations\AnnotationRegistry::registerFile('../../Core/vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php');
        
        $config = new \Doctrine\ORM\Configuration();
        $config->setAutoGenerateProxyClasses($this->getEnvironment() == 'dev' || $this->deleteCache);
        $config->setProxyDir($this->_config->doctrine['proxyDir'] . DIRECTORY_SEPARATOR . $this->getEnvironment());
        $config->setProxyNamespace($this->_config->doctrine['proxyNameSpace']);
        $config->setEntityNamespaces(array(self::ENTITY_NAMESPACE, 'Shared\Main'));

//		if ($this->getEnvironment() != 'dev') {
//			$memcache = new \Memcache();
//			foreach ($this->getConfig()->memcacheServers as $cacheServer) {
//				$memcache->connect($cacheServer->host, $cacheServer->port);
//			}
//			$this->cacheDriver = $this->getDoctrineCacheDriverInstance($memcache);
//			$this->setCacheDrivers($config, $memcache);
//		} else {
//			$this->cacheDriver = new \Doctrine\Common\Cache\ArrayCache;
//		}

        $this->cacheDriver      = new \Doctrine\Common\Cache\ArrayCache;
        $this->AnnotationReader = new \Doctrine\Common\Annotations\CachedReader(new \Doctrine\Common\Annotations\AnnotationReader(), $this->cacheDriver);
        $driverImpl             = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($this->AnnotationReader, array(__DIR__ . '/Main'));
        $driverImplMultimedia   = new \Doctrine\ORM\Mapping\Driver\AnnotationDriver($this->AnnotationReader, array(__DIR__ . '/Multimedia'));

        $driverChain = new \Doctrine\ORM\Mapping\Driver\DriverChain();
        $driverChain->addDriver($driverImpl, self::ENTITY_NAMESPACE);
        $driverChain->addDriver($driverImplMultimedia, 'Shared\Multimedia');

        \Gedmo\DoctrineExtensions::registerAbstractMappingIntoDriverChainORM(
                $driverChain, $this->AnnotationReader
        );

        $config->setMetadataDriverImpl($driverChain);

        if ($this->getEnvironment() != 'dev' and filter_input(INPUT_GET, 'queries')) {
            $config->setSQLLogger(new SQLLogger);
        }

        $eventManager = new \Doctrine\Common\EventManager;
        $eventManager->addEventSubscriber(new \Doctrine\DBAL\Event\Listeners\MysqlSessionInit('utf8'));
//		$resolveTargetEntityListener = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
//		$resolveTargetEntityListener->addResolveTargetEntity('Media\Entity\ObjectInterface', 'Shared\Entity\SiteObject', array());
//
//		$eventManager->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $resolveTargetEntityListener);

        $treeListener = new \Gedmo\Tree\TreeListener;
        $treeListener->setAnnotationReader($this->AnnotationReader);
        $eventManager->addEventSubscriber($treeListener);

        $sluggableListener = new \Gedmo\Sluggable\SluggableListener;
        $sluggableListener->setAnnotationReader($this->AnnotationReader);
        $sluggableListener->setTransliterator(array('Shared\URLManagement\Transliterator', 'transliterate'));
        $sluggableListener->setUrlizer(array('Shared\URLManagement\Transliterator', 'urlize'));
        $eventManager->addEventSubscriber($sluggableListener);

        $config->addCustomStringFunction('CASEWHEN', 'Shared\Doctrine\Mysql\StringFunction\Casewhen');
        $config->addCustomStringFunction('Field', 'Shared\Doctrine\Mysql\StringFunction\Field');
        $config->addCustomStringFunction('YEAR', 'Shared\Doctrine\Mysql\StringFunction\Year');
        $config->addCustomStringFunction('GROUP_CONCAT', 'Shared\Doctrine\Mysql\StringFunction\GroupConcat');

        \Doctrine\DBAL\Types\Type::addType(IpType::NAME, 'Shared\Doctrine\DBAL\DataType\IpType');

//		$config->addFilter('softDeleteable', 'Shared\Filter\SoftDeleteableFilter');
        $this->_entityManager = EntityManager::create($this->_config->doctrine['dbParams'], $config, $eventManager);
//		$this->entityManager->getFilters()->enable('softDeleteable');
    }

    public function createImpression() {
        $Impression = new Main\Impression();
        $Impression->setIp($this->_request->getClientIp());
        $Impression->setUserAgent($this->_request->headers->get('User-Agent'));
        $Impression->setUrl($this->_request->getUri());
        $Impression->setRoute($this->getCurrentRoute());
        $Impression->setDateTime(date('Y-m-d H:i:s'));
        
        $this->_entityManager->persist($Impression);
        $this->_entityManager->flush();
    }
    
    /**
     * 
     * @return \Shared\FronController
     */
    public function getFrontController() {
        return $this->_frontController;
    }

    /**
     * @return \Symfony\Component\HttpFoundation\Request
     */
    public function getRequest() {
        return $this->_request;
    }

    public function getSession() {
        $Session = \System\Session::getInstance();
        return $Session;
    }

    public function getHost() {
        $script = substr($_SERVER['SCRIPT_NAME'], 1) != 'app.php' ? '/' . substr($_SERVER['SCRIPT_NAME'], 1) : '';
//        $this->host = $this->getProtocol() . '://' . $_SERVER['SERVER_NAME'] . $script;
        $this->host = $script;
        return $this->host;
    }

    public function getProtocol() {
        return strtolower(substr($_SERVER["SERVER_PROTOCOL"], 0, 5)) == 'https' ? 'https' : 'http';
    }

    public function getRoute() {
        return array('name' => $this->currentRoute);
    }

    public function getCurrentRoute() {
        return $this->currentRoute;
    }

    public function setCurrentRoute($currentRoute) {
        $this->currentRoute = $currentRoute;
    }

    public function getDeleteCache() {
        return $this->deleteCache;
    }

    public function setDeleteCache($deleteCache) {
        $this->deleteCache = $deleteCache;
    }

    public function getDir() {
        return $this->dir;
    }

    public function setDir($dir) {
        $this->dir = $dir;
    }

    public function getAnnotationReader() {
        return $this->AnnotationReader;
    }

    public function getLocale() {
        return $this->locale;
    }

    public function setLocale($locale) {
        $this->locale = $locale;
    }
    
    public function getCleanUri() {
        return str_replace('?' . $this->getRequest()->getQueryString(), '', $this->getRequest()->getUri());
    }
    
    public function getFilesDir() {
        return $this->filesDir;
    }

    public function setFilesDir($filesDir) {
        $this->filesDir = $filesDir;
    }
}
